from __future__ import with_statement

from khronos.utils import INF, CPUTracker, Namespace, Logger, TimeDelta

class Solver(object):
    """Generic solver interface. All solvers in the metaheuristics package are subclasses of 
    this class. It merely contains a small set of attributes and utility methods that should 
    be common to all solver classes."""
    __slots__ = ["instance", "solutions", "cpu", "log"]
    
    def __init__(self):
        self.instance = None
        self.solutions = []
        self.cpu = CPUTracker()
        self.log = Logger()
        
    def solve(self, instance, cpu=INF, iterations=INF):
        self.init(instance)
        self.run(cpu, iterations)
        
    def init(self, instance):
        self.instance = instance
        self.solutions = []
        self.cpu.clear(force=True)
        
    def run(self, cpu=INF, iterations=INF):
        try:
            with self.cpu.tracking():
                iteration = 0
                while self.cpu.total() < cpu and iteration < iterations:
                    self.iterate(cpu)
                    iteration += 1
        except KeyboardInterrupt:
            pass
            
    def step(self, n=1):
        with self.cpu.tracking():
            for _ in xrange(n):
                self.iterate()
                
    def iterate(self):
        raise NotImplementedError()
        
    def add_solution(self, solution, z):
        sol = Namespace(Solution=solution, Objective=z, CPU=self.cpu.total())
        self.solutions.append(sol)
        self.log.info("Stored solution with objective value %s at %s" % 
                      (z, TimeDelta(seconds=sol.CPU)))
        
    def plot_solutions(self, filename=None, dpi=300):
        """Build and display or save a plot showing the evolution of solutions' objective function 
        value over time. If filename is None, the plot is displayed to the screen, otherwise it is 
        saved to the specified file. The filename extension will determine the format of the output 
        file (e.g. pdf, eps, png, jpg)."""
        if len(self.solutions) == 0:
            self.log.error("No solutions to be plotted.")
            return
        sol = self.solutions[0]
        cpu = sol.CPU
        objective = sol.Objective
        xs = [cpu]
        ys = [objective]
        for sol in self.solutions[1:]:
            cpu = sol.CPU
            xs.append(cpu)
            ys.append(objective)
            objective = sol.Objective
            xs.append(cpu)
            ys.append(objective)
        xs.append(self.cpu.total())
        ys.append(objective)
        
        from matplotlib.pyplot import figure
        fig = figure()
        plot = fig.add_subplot(1, 1, 1)
        plot.set_title("Objective function over time")
        plot.set_xlabel("CPU time (s)")
        plot.set_ylabel("Objective")
        plot.plot(xs, ys, color="blue", linewidth=2, 
                  marker="o", markeredgecolor="blue")
        if filename is None:
            fig.show()
        else:
            fig.savefig(filename, dpi=dpi)
            
            
